using System;
using System.Collections.Generic;

namespace CodeBackpack.Plist
{
    public class PropertyList
    {
        private Object value = null;

        public static PropertyList CreateList()
        {
            PropertyList plist = new PropertyList();
            plist.TreatAs<List<PropertyList>>();
            return plist;
        }

        public static PropertyList CreateDict()
        {
            PropertyList plist = new PropertyList();
            plist.TreatAs<LinkedHashMap<String, PropertyList>>();
            return plist;
        }

        public Object Value
        {
            get { return value; }
        }

        public byte[] Bytes
        {
            get { return (byte[]) value; }
            set { this.value = value; }
        }

        public String String
        {
            get { return (String) value; }
            set { this.value = value; }
        }

        public int Int
        {
            get { return value is long ? (int) ((long) value) : (int) value; }
            set { this.value = value; }
        }

        public long Long
        {
            get { return value is int ? (int) value : (long) value; }
            set { this.value = value; }
        }

        public double Double
        {
            get { return (double) value; }
            set { this.value = value; }
        }

        public bool Bool
        {
            get { return (bool) value; }
            set { this.value = value; }
        }

        public DateTime DateTime
        {
            get { return (DateTime) value; }
            set { this.value = value; }
        }

        public IList<PropertyList> List
        {
            get { return TreatAs<List<PropertyList>>(); }
        }

        public PropertyList this[int listIndex]
        {
            get
            {
                IList<PropertyList> list = List;
                int n = listIndex - list.Count + 1;
                for (int i = 0; n > 0 && i < n; i++)
                {
                    list.Add(new PropertyList());
                }
                return list[listIndex];
            }
        }

        public IDictionary<String, PropertyList> Dict
        {
            get { return TreatAs<LinkedHashMap<String, PropertyList>>(); }
        }

        public PropertyList this[String dictKey]
        {
            get
            {
                IDictionary<String, PropertyList> dict = Dict;
                PropertyList plist;
                if (!dict.TryGetValue(dictKey, out plist))
                {
                    plist = new PropertyList();
                    dict.Add(dictKey, plist);
                }
                return plist;
            }
        }

        private T TreatAs<T>() where T : class, new()
        {
            if (value != null && value is T)
            {
                return value as T;
            }
            else
            {
                T instance = new T();
                value = instance;
                return instance;
            }
        }

        public void Save(PropertyListWriter writer)
        {
            writer.WriteProlog();
            if (value != null)
            {
                WritePlist(writer);
            }
            writer.WriteEpilog();
        }

        public PropertyList Load(PropertyListReader reader)
        {
            PropertyList plist = null;
            reader.ReadProlog();
            if (!reader.IsEmpty())
            {
                plist = ReadPlist(reader);
            }
            reader.ReadEpilog();
            value = plist != null ? plist.value : null;
            return this;
        }

        private void WritePlist(PropertyListWriter writer)
        {
            if (value is byte[])
            {
                writer.WriteScalar(value as byte[]);
            }
            else if (value is String)
            {
                writer.WriteScalar(value as String);
            }
            else if (value is int)
            {
                writer.WriteScalar((int) value);
            }
            else if (value is long)
            {
                writer.WriteScalar((long) value);
            }
            else if (value is double)
            {
                writer.WriteScalar((double) value);
            }
            else if (value is DateTime)
            {
                writer.WriteScalar(((DateTime) value).ToUniversalTime());
            }
            else if (value is bool)
            {
                writer.WriteScalar((bool) value);
            }
            else if (value is IList<PropertyList>)
            {
                writer.WriteListStart();
                foreach (PropertyList item in (IList<PropertyList>) value)
                {
                    if (item.value != null)
                    {
                        item.WritePlist(writer);
                    }
                }
                writer.WriteContainerEnd();
            }
            else if (value is IDictionary<String, PropertyList>)
            {
                writer.WriteDictStart();
                foreach (KeyValuePair<String, PropertyList> pair in (IDictionary<String, PropertyList>) value)
                {
                    if (pair.Value.value != null)
                    {
                        writer.WriteDictKey(pair.Key);
                        pair.Value.WritePlist(writer);
                    }
                }
                writer.WriteContainerEnd();
            }
        }

        private static PropertyList ReadPlist(PropertyListReader reader)
        {
            if (reader.AtListStart())
            {
                PropertyList list = CreateList();
                while (!reader.AtContainerEnd())
                {
                    list.List.Add(ReadPlist(reader));
                }
                return list;
            }
            else if (reader.AtDictStart())
            {
                PropertyList dict = CreateDict();
                while (!reader.AtContainerEnd())
                {
                    dict.Dict.Add(reader.ReadDictKey(), ReadPlist(reader));
                }
                return dict;
            }
            else
            {
                PropertyList plist = new PropertyList();
                plist.value = reader.ReadScalar();
                return plist;
            }
        }
    }
}